import * as THREE from 'three';
import { Easing, Interpolation, Tween } from '@tweenjs/tween.js';
import BasicScene, { BasicSceneOptions, BasicItemInfo, CameraInfo, Default3DAxis } from './BasicScene';
import { PlayerObject } from 'skinview3d';
import HumanScene from './ItemScene/HumanScene';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

function calcItemPos(radius: number, count: number, y = 0) {
  const r = (Math.PI * 2) / count,
    res = [];
  for (let i = 0; i < count; i++) {
    res.push([
      [Math.sin(r * i) * radius, y, Math.cos(r * i) * radius],
      [0, r * i + Math.PI, 0],
    ]);
  }

  return res;
}

export interface InteractiveMethods {
  enter?: (g: THREE.Object3D, human?: PlayerObject) => void;
  leave?: (g: THREE.Object3D, human?: PlayerObject) => void;
}

interface SingleItemInfo {
  obj: THREE.Object3D;
  InteractiveMap?: Record<string, InteractiveMethods>;
}

export interface ItemSceneInfo extends BasicItemInfo {
  /** 物品列表 */
  objs: Array<SingleItemInfo>;
  /** 人模场景 */
  humanScene?: HumanScene;
  onBeforeChangeCameraPosition?: (sourcePositionInfo: CameraInfo, newPositioninfo: CameraInfo) => CameraInfo | void;
  onFinishSetPosition?: (objs: Array<SingleItemInfo>) => void;
  onCurrentEnter?: () => void;
  onCurrnetLeave?: () => void;
  onCurrentBeforeEnter3D?: (newPositioninfo: CameraInfo) => CameraInfo | void;
  onCurrentEnter3D?: () => void;
  onCurrentBeforeLeave3D?: () => void;
  onCurrentLeave3D?: () => void;
}

export interface ItemSceneOptions extends BasicSceneOptions<ItemSceneInfo> {
  radius?: number;
  ItemY?: number;
  renderer: THREE.WebGLRenderer;
}

export default class ItemScene extends BasicScene {
  camera: THREE.Camera;
  renderer: THREE.WebGLRenderer;
  tweenP: Tween<Default3DAxis>;
  tweenR: Tween<Default3DAxis>;
  radius: number;
  // CurrentCameraInfo: {
  //   P: Default3DAxis;
  //   R: Default3DAxis;
  // };
  CurrentItem: string;
  controls: OrbitControls;
  isEnterView: boolean;
  private ItemY: number;
  declare ItemList: Record<string, ItemSceneInfo>;

  constructor({ id, camera, scene, ItemList, radius = 50, ItemY = -10, renderer, canvas }: ItemSceneOptions) {
    super({ id, camera, scene, ItemList, canvas });
    this.camera = camera;
    this.tweenP = new Tween({ x: 0, y: 0, z: 0 }).duration(300);
    this.tweenR = new Tween({ x: 0, y: 0, z: 0 }).duration(300);
    this.radius = radius;
    this.ItemY = ItemY;
    this.CurrentItem = '';
    this.renderer = renderer;
    this.controls = new OrbitControls(this.camera, this.canvas);
    this.isEnterView = false;
    this.controls.enabled = false;

    const ItemCount = Object.keys(ItemList).length,
      PR = calcItemPos(radius, ItemCount, ItemY);
    let PosIndex = 0;

    // controls init
    const c = this.controls;
    // c.enablePan = false;
    c.minDistance = 2;
    c.maxDistance = 10;

    for (const i in this.ItemList) {
      if (Object.prototype.hasOwnProperty.call(this.ItemList, i)) {
        const len = this.ItemList[i].objs.length;
        for (let j = 0; j < len; j++) {
          const { objs } = this.ItemList[i],
            arr0 = PR[PosIndex][0],
            arr1 = PR[PosIndex][1],
            { obj } = objs[j];
          PosIndex++;
          obj.position.set(arr0[0], arr0[1], arr0[2]);
          obj.rotation.set(arr1[0], arr1[1], arr1[2]);
        }
      }
    }
  }

  private _hasItem(id: string) {
    const o = this.ItemList[id];
    if (!o) {
      console.warn('undefined item: ' + id);
      return;
    }
    return o;
  }

  SetCamera(info: CameraInfo, cb?: Function) {
    const { P, R } = info;
    this.tweenP = new Tween(this.camera.position)
      .to({ ...P }, 1000)
      .interpolation(Interpolation.Bezier)
      .easing(Easing.Quadratic.InOut)
      .onUpdate(({ x, y, z }) => {
        this.camera.position.set(x, y, z);
      })
      .onComplete(p => {
        cb && cb(p);
      })
      .start();
    this.tweenR = new Tween(this.camera.rotation)
      .interpolation(Interpolation.Bezier)
      .easing(Easing.Quadratic.InOut)
      .onUpdate(({ x, y, z }) => {
        this.camera.rotation.set(x, y, z);
      })

      .to({ ...R }, 1000)
      .start();
  }

  ChangeShowItem(id: string) {
    const o = this._hasItem(id);
    if (!o) return;
    this.CurrentItem = id;
    const P = o.objs[0].obj.position,
      R = o.objs[0].obj.rotation,
      InitCameraInfo = {
        P: P.clone().set(P.x * 0.75, P.y * 0.8, P.z * 0.75),
        R: {
          x: R.x,
          y: R.y - Math.PI * 1.85,
          z: R.z,
        },
      },
      FinalCamera = o.onBeforeChangeCameraPosition
        ? o.onBeforeChangeCameraPosition({ P, R }, InitCameraInfo) ?? InitCameraInfo
        : InitCameraInfo;

    this.SetCamera(FinalCamera);

    return this;
  }

  Enter3DView() {
    this.DisabledControl();
    const o = this._hasItem(this.CurrentItem)!,
      P = o.objs[0].obj.position,
      r = o.objs[0].obj.rotation;
    const InitCameraInfo = {
      P: { x: P.x * 0.8, y: P.y + 2, z: P.z * 0.8 },
      R: { x: r.x, y: r.y - Math.PI * 2, z: r.z },
    };
    const FinalCamera = o.onCurrentBeforeEnter3D
      ? o.onCurrentBeforeEnter3D(InitCameraInfo) ?? InitCameraInfo
      : InitCameraInfo;
    this.SetCamera(FinalCamera, () => {
      this.EnableControl();
    });

    o.onCurrentEnter3D && o.onCurrentEnter3D();
    const finalTarget = o.objs[0].obj.position.clone();
    finalTarget.setY(P.y + 2);
    this.controls.target = finalTarget;
    return this;
  }
  Exit3DView() {
    const o = this._hasItem(this.CurrentItem)!;
    this.ChangeShowItem(this.CurrentItem);
    o.onCurrentLeave3D && o.onCurrentLeave3D();
    // @ts-ignore: close control
    this.DisabledControl();
    return this;
  }

  EnableControl() {
    this.controls.enabled = true;
    this.isEnterView = true;
  }

  DisabledControl() {
    this.controls.enabled = false;
    this.isEnterView = false;
  }

  StopTween() {
    this.tweenP.stop();
    this.tweenR.stop();
  }

  onSceneChange(id: string) {
    if (id != this.id) {
      this.StopTween();
      this.onCurrentSceneLeave && this.onCurrentSceneLeave();
      return;
    } else {
      this.onCurrentSceneEnter && this.onCurrentSceneEnter();
    }
  }

  animate() {
    this.tweenP.update();
    this.tweenR.update();
    // if (this.isEnterView) this.controls.update();
  }
}
